package agents;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import negotiator.Agent;
import negotiator.Bid;
import negotiator.BidIterator;
import negotiator.actions.Accept;
import negotiator.actions.Action;
import negotiator.actions.EndNegotiation;
import negotiator.actions.Offer;
import negotiator.issue.Issue;
import negotiator.issue.IssueDiscrete;
import negotiator.issue.IssueInteger;
import negotiator.issue.IssueReal;
import negotiator.issue.Value;
import negotiator.issue.ValueDiscrete;
import negotiator.issue.ValueInteger;
import negotiator.issue.ValueReal;

public class LCRTAgent extends Agent {

	private ArrayList<Offer> history = new ArrayList<Offer>();
	private double minUtil;
	private ArrayList<Issue> issues;
	private double lambda0 = .08, lambda_original; // Lambda needs an initial value
	private double lT = 0; // Acceptance threshold of agent at time t
	private double delta, uMax = 1., alpha = 1.8;
	private double epsilon = 0.05;
	private double eta = 0.9;
	//
	private int[][] offerCounter, sigmaCounter;
	private int moveCount = 0;
	private int numIssues;
	private final int recentHistory = 10;
	private double[][] issueCounter;
	private Offer omegaBest = null; // To notify NB component of proposing this
									// bid next time
	private double ru0 = 0, rut = 0;
	//
	private final double gamma = 9.5, weight = 0.12;
	private double sigma, lambda;
	private double nextTime = 0.001;

	public static String getVersion() {
		return "2.1";
	}
	
	@Override
	public String getName() {
		return "LCRT Agent";
	}

	/**
	 * init is called when a next session starts with the same opponent.
	 */
	@Override
	public void init() {
		minUtil = 1;
		delta = utilitySpace.getDiscountFactor();
		try {
			uMax = utilitySpace.getUtility(utilitySpace.getMaxUtilityBid());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("umax: " + uMax);
		System.out.println("delta: " + delta);
		if (delta > 0)
			lambda_original = lambda0 + ((1. - lambda0) * Math.pow(delta, 1.75));
		else
			lambda_original = lambda0;
		//
		lambda = lambda_original;
		System.out.println("Original lambda: " + lambda);
		numIssues = utilitySpace.getDomain().getIssues().size();
		offerCounter = new int[numIssues][];
		issueCounter = new double[numIssues][];
		sigmaCounter = new int[numIssues][];
		issues = utilitySpace.getDomain().getIssues();

		createCounter(sigmaCounter);
		createCounter(offerCounter);
		//
		int i = 0;
		for (Issue iss : issues) {
			switch (iss.getType()) {
			case DISCRETE:
				IssueDiscrete lIssueDiscrete = (IssueDiscrete) iss;
				issueCounter[i] = new double[lIssueDiscrete.getNumberOfValues()];
				break;
			case REAL:
				IssueReal lIssueReal = (IssueReal) iss;
				issueCounter[i] = new double[lIssueReal.getNumberOfDiscretizationSteps()];
				break;
			case INTEGER:
				IssueInteger lIssueInteger = (IssueInteger) iss;
				issueCounter[i] = new double[lIssueInteger.getUpperBound()
						- lIssueInteger.getLowerBound()];
				break;
			default:
				break;
			}
			i++;
		}
		if (utilitySpace.getReservationValue() != null)
			rut = utilitySpace.getReservationValue();
	}

	private void createCounter(int[][] counter) {
		int i = 0;
		for (Issue iss : issues) {
			switch (iss.getType()) {
			case DISCRETE:
				IssueDiscrete lIssueDiscrete = (IssueDiscrete) iss;
				counter[i] = new int[lIssueDiscrete.getNumberOfValues()];
				break;
			case REAL:
				IssueReal lIssueReal = (IssueReal) iss;
				counter[i] = new int[lIssueReal.getNumberOfDiscretizationSteps()];
				break;
			case INTEGER:
				IssueInteger lIssueInteger = (IssueInteger) iss;
				counter[i] = new int[lIssueInteger.getUpperBound() - lIssueInteger.getLowerBound()];
				break;
			default:
				break;
			}
			i++;
		}
	}

	private void updateCounters(Offer offer) {
		Bid b = offer.getBid();
		moveCount++;
		if (moveCount == recentHistory) {
			moveCount = 0;
			sigmaCounter = new int[numIssues][];
			createCounter(sigmaCounter);
		}
		for (int i = 0; i < offerCounter.length; i++) {
			Issue iss = issues.get(i);
			Value v = null;
			try {
				v = b.getValue(iss.getNumber());
			} catch (Exception e) {
				e.printStackTrace();
			}
			switch (v.getType()) {
			case DISCRETE:
				ValueDiscrete vd = (ValueDiscrete) v;
				IssueDiscrete lIssueDiscrete = (IssueDiscrete) iss;
				int index = lIssueDiscrete.getValueIndex(vd.getValue());
				offerCounter[i][index]++;
				sigmaCounter[i][index]++;
				issueCounter[i][index] = issueCounter[i][index]
						+ Math.pow(eta, offerCounter[i][index]);
				break;
			case REAL:
				ValueReal vr = (ValueReal) v;
				IssueReal ireal = (IssueReal) iss;
				double binsize = (ireal.getUpperBound() - ireal.getLowerBound())
						/ (double) ireal.getNumberOfDiscretizationSteps();
				double bin = vr.getValue() / binsize;
				index = (int) bin;
				offerCounter[i][index]++;
				sigmaCounter[i][index]++;
				issueCounter[i][index] = issueCounter[i][index]
						+ Math.pow(eta, offerCounter[i][index]);
				break;
			case INTEGER:
				ValueInteger vi = (ValueInteger) v;
				index = vi.getValue();
				offerCounter[i][index]++;
				sigmaCounter[i][index]++;
				issueCounter[i][index] = issueCounter[i][index]
						+ Math.pow(eta, offerCounter[i][index]);
				break;
			default:
				break;
			}
		}
	}

	/*
	 * private void updateIssueCounters() { for (int i = 0; i < offerCounter.length; i++) { for (int j = 0; j <
	 * offerCounter[i].length; j++) { issueCounter[i][j] = issueCounter[i][j] + Math.pow(eta, offerCounter[i][j]); } } }
	 */
	private double calculateFOmega(Bid b) {
		double f = 0;

		for (int i = 0; i < offerCounter.length; i++) {
			Issue iss = (Issue) issues.get(i);
			Value v = null;
			try {
				v = b.getValue(iss.getNumber());
			} catch (Exception e) {
				e.printStackTrace();
			}
			switch (v.getType()) {
			case DISCRETE:
				ValueDiscrete vd = (ValueDiscrete) v;
				IssueDiscrete lIssueDiscrete = (IssueDiscrete) iss;
				f += issueCounter[i][lIssueDiscrete.getValueIndex(vd.getValue())];
				break;
			case REAL:
				ValueReal vr = (ValueReal) v;
				IssueReal ireal = (IssueReal) iss;
				double binsize = (ireal.getUpperBound() - ireal.getLowerBound())
						/ (double) ireal.getNumberOfDiscretizationSteps();
				double bin = vr.getValue() / binsize;
				f += issueCounter[i][(int) bin];
				break;
			case INTEGER:
				ValueInteger vi = (ValueInteger) v;
				f += issueCounter[i][vi.getValue()];
				break;
			default:
				break;
			}
		}

		return f;
	}

	public void ReceiveMessage(Action opponentAction) {
		// Accept, EndNegotiation, IllegalAction, Offer
		if (opponentAction instanceof Offer) {
			history.add((Offer) opponentAction);
			// update counters here
			updateCounters((Offer) opponentAction);
		}
	}

	public Action chooseAction() {
		Action action = null;

		setLT(timeline.getTime());
		setLambda(timeline.getTime());
		Offer offer = new Offer(this, nextBid());
		rut = utilitySpace.getReservationValueWithDiscount(timeline);
		if (history.isEmpty()) {
			if (!terminateCondition(history, timeline.getTime(), offer)) {
				action = offer;
			} else {
				action = new EndNegotiation();
			}
		} else {
			try {
				double concesD = getConcessionDegree(timeline.getTime());
				if (utilitySpace.getUtility(history.get(history.size() - 1).getBid()) >= minUtil - concesD) {
					return new Accept(getAgentID());
				} 
			}
			catch (Exception e) {};
			if (acceptCondition(history, timeline.getTime(), offer)
					&& !terminateCondition(history, timeline.getTime(), offer)) {
				action = new Accept(getAgentID());
			} else if (!acceptCondition(history, timeline.getTime(), offer)
					&& terminateCondition(history, timeline.getTime(), offer)) {
				action = new EndNegotiation();
			} else if (acceptCondition(history, timeline.getTime(), offer)
					&& terminateCondition(history, timeline.getTime(), offer)) {
				if (getUtility(history.get(history.size() - 1).getBid()) > rut) {
					System.out.println("LCRT --- ACCEPT");
					action = new Accept(getAgentID());
				} else {
					action = new EndNegotiation();
				}
			} else {
				action = offer;
			}
		}

		if (omegaBest != null) {
			action = omegaBest;
			omegaBest = null;
		} else {
			action = offer;
		}
		// System.out.println("Returning offer: [" + getUtility(action.getBidFromAction(action)) + "] "+
		// action.toString());
		return action;
	}

	public boolean terminateCondition(List<Offer> history, double time, Offer offer) {
		try {
			if (ru0 > lT || ru0 > utilitySpace.getUtility(offer.getBid())) {
				return true;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}

	public boolean acceptCondition(List<Offer> history, double time, Offer offer) {
		Offer omega1 = history.get(history.size() - 1);
		//
		Offer bestOutcome = null;
		double maxUtil = Double.NEGATIVE_INFINITY, concesD = getConcessionDegree(timeline.getTime());
		for (Offer curOffer : history) {
			double curUtil;
			try {
				curUtil = utilitySpace.getUtility(curOffer.getBid());
				if (curUtil > maxUtil) {
					maxUtil = curUtil;
					bestOutcome = curOffer;
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//
		double omega1Util;
		try {
			omega1Util = utilitySpace.getUtility(omega1.getBid());
			double offerUtil = utilitySpace.getUtility(offer.getBid());
			double bestOutcomeUtil = utilitySpace.getUtility(bestOutcome.getBid());
			if(omega1Util >= minUtil) {
				return true;
			}
			if (omega1Util >= (lT - concesD) || omega1Util >= offerUtil) {
				return true;
			} else if (bestOutcomeUtil > (lT - concesD) || bestOutcomeUtil > offerUtil) {
				omegaBest = bestOutcome;
				return false;
			} else {
				return false;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
	}

	public Bid nextBid() {
		BidIterator iterator = new BidIterator(utilitySpace.getDomain());
		ArrayList<Bid> bids = new ArrayList<Bid>();
		double highestF = 0.;
		Bid omegaMax = null, bid = null;
		double util = 0., concesD = getConcessionDegree(timeline.getTime());
		//System.out.println("CondesD: " + concesD + " lt-c: " + (lT - concesD));
		while (iterator.hasNext()) {
			bid = iterator.next();
			try {
				util = utilitySpace.getUtility(bid);
				if (util >= (lT - concesD)) {
					bids.add(bid);
					double f = calculateFOmega(bid);
					if (f >= highestF) {
						highestF = f;
						omegaMax = bid;
					}
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		Random random = new Random();
		if (random.nextDouble() >= epsilon) {
			try {
				minUtil = utilitySpace.getUtility(omegaMax);
			} catch (Exception e) {};
			return omegaMax;
		} else {
			bid = bids.get(random.nextInt(bids.size()));
			return bid;
		}
	}
	
	double cWeight = 0.5;
	private double getConcessionDegree(double time) {
		if(time < 0.8) {
			return 0;
		} else {
			return Math.max(((1. - time) - (cWeight * sigma)), 0);
		}
	}

	public void setLambda(double time) {
		setSigma();
		lambda = lambda_original + (weight * (1 - lambda_original) * Math.pow(sigma, gamma));
	}

	private void setSigma() {
		//
		double counter = 0, total = 0;
		for (int i = 0; i < sigmaCounter.length; i++) {
			for (int j = 0; j < sigmaCounter[i].length; j++) {
				total++;
				if (sigmaCounter[i][j] > 0) {
					counter++;
				}
			}
		}
		sigma = ((counter - numIssues) / total);
		if (sigma < 0)
			sigma = 0;
	}

	public void setLT(double time) {
		if (time < lambda) {
			lT = uMax - (uMax - ((uMax * delta) / Math.pow(delta, lambda)))
					* Math.pow((time / lambda),	alpha);
		} else {
			lT = (uMax * delta) / Math.pow(delta, time);
		}
		if (time >= nextTime) {
			System.out.println("Time [" + time + "] lT [" + lT + "] lambda [" + lambda + "]");
			nextTime += nextTime;
		}
		if (time > 0.9) System.out.println("Time [" + time + "] lT [" + lT + "] lambda [" + lambda + "] minUtil ["+minUtil+"]");
	}
}